package core.network;

import java.io.IOException;
import java.io.Serializable;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

import core.network.marshal.MarshalUtils;
import core.servlets.HomeServlet;
import core.util.IpPortPair;
import core.util.StoredData;
import core.util.UserSessionData;
import core.util.Utilities;

public class RPCListenThread extends Thread {
	private DatagramSocket socket;

	private HomeServlet servlet; // needed in order to get serverId and to
									// update session states

	public RPCListenThread(HomeServlet servlet) {
		this.setDaemon(true);
		this.servlet = servlet;
		this.socket = servlet.getOutSocket();
	}

	public void run() {
		DatagramPacket receivedPacket = null;
		DatagramPacket packetToSend = null;
		InetAddress returnAddress = null;
		int returnPort;
		byte[] outBuf;

		while (true) {
			receivedPacket = new DatagramPacket(
					new byte[Utilities.MAX_PACKET_SIZE],
					Utilities.MAX_PACKET_SIZE);
			try {
				socket.receive(receivedPacket);
			} catch (IOException e) {
				continue;
			}
			returnAddress = receivedPacket.getAddress();
			returnPort = receivedPacket.getPort();
			System.out.println("Received UDP packet from " + returnPort);
			// get data from packet
			byte[] dataBytes = receivedPacket.getData();
			Object obj = null;
			try {
				obj = MarshalUtils.byteArrayToObject(dataBytes);
			} catch (IOException e) {
				System.err.println("Input request cannot be read");
				e.printStackTrace();
				obj = null;
			}

			if (obj == null) {
				System.out.println("This is an enpty packet");
				continue;
			}
			// we need to process this data based on two types of messages
			Serializable resp = null;
			if (obj instanceof GetRequest) {
				System.out.println("This is a GET REQUEST");
				resp = computeGetResponse((GetRequest) obj);
			} else if (obj instanceof PingRequest) {
				System.out.println("This is a PING REQUEST");
				resp = new PingResponse(new IpPortPair(servlet.getIP(),
						servlet.getServletPort()));
			} else if (obj instanceof PutRequest) {
				System.out.println("This is a PUT REQUEST");
				resp = computePutResponse((PutRequest) obj);
			} else if (obj instanceof PutResponse) {
				System.out.println("This is a PUT RESPONSE");
				PutResponse putr = (PutResponse) obj;
				StoredData.putResponseCounter.get(putr.getCallId())
						.incrementCounter();
				StoredData.putResponseCounter.get(putr.getCallId())
						.addLocation(putr.getServerId());
			} else if (obj instanceof GetResponse) {
				System.out.println("This is a GET RESPONSE");
				GetResponse getr = (GetResponse) obj;
				StoredData.getResponse.put(getr.getCallId(),
						getr.getSessionData());
			} else if (obj instanceof PingResponse) {
				System.out.println("This is a PING RESPONSE");
				PingResponse pingr = (PingResponse) obj;
				StoredData.pingResponse.put(pingr.getPair(), true);
			}

			if (resp == null)
				continue;

			try {
				outBuf = MarshalUtils.objectToByteArray(resp);
			} catch (IOException e) {
				outBuf = null;
			}
			if (outBuf != null) {
				packetToSend = new DatagramPacket(outBuf, outBuf.length,
						returnAddress, returnPort);
				try {
					socket.send(packetToSend);
				} catch (IOException e) {
					System.out.println("Responding to incoming packet failed.");
				}
			}
		}
	}

	private Serializable computePutResponse(PutRequest request) {
		UserSessionData data = request.getData();
		String sessionId = data.getCookieValue().getSessionId();

		StoredData.storedSessionData.put(sessionId, data);

		PutResponse resp = new PutResponse(request.getCallId(),
				servlet.getServerId());
		return resp;
	}

	private GetResponse computeGetResponse(GetRequest request) {
		UserSessionData data;
		// need to look up session id in table.
		data = StoredData.storedSessionData.get(request.getSessionId());
		// returns a copy...
		if (data == null)
			return null;
		GetResponse resp = new GetResponse(request.getCallId(), data);
		return resp;
	}
}